#include <user.h>
#include <ts7200.h>
#include <defs.h>
#include <vic.h>
#include <uart.h>
#include <iobuf.h>
#include <isr.h>
#include <train.h>
#include <bwio.h>

void TrainRxNotifier() {
  int msg;

  uart *const uart1 = (uart*)( UART1_BASE );

  int sender_tid;
  for ( ; ; ) {
    AwaitEvent(EVENT_UART1_RX, NULL, 0);
    msg = uart1->data;
    if (uart1->rx_sts & (FE_MASK | PE_MASK | BE_MASK | OE_MASK)) {
      continue;
    }
    Receive(&sender_tid, NULL, 0);
    Reply(sender_tid, (char*)&msg, sizeof(int));
  }
  Exit();
}

void TrainRxCourier() {
  int c;

  int notifier_tid = Create(8, &TrainRxNotifier, "train-rx-notifier");
  Execute(notifier_tid);
  
  int server_tid = WhoIs("train-rx-server");

  for ( ; ; ) {
    Send(notifier_tid, NULL, 0, (char*)&c, sizeof(int));
    Send(server_tid, (char*)&c, sizeof(int), NULL, 0);
  }
  Exit();
}

void TrainRxServer() { 
  int i_min;
  iobuf buf;
  IobufInit(&buf);

  // Getc() queue
  iobuf request_queue;
  IobufInit(&request_queue);
 
  int msg;  
  rx_reply reply;

  int courier_tid = Create(8, &TrainRxCourier, "train-rx-courier");
  Execute(courier_tid);

  int sender_tid;
  uart *const uart1 = (uart*)( UART1_BASE );
  for ( ; ; ) {
    Receive(&sender_tid, (char*)&msg, sizeof(int));
    if (sender_tid == courier_tid) {
      if (IobufFull(&buf))
        Panic("TrainRxServer", "IO buffer full");

      // courier has data for me
      IobufEnqueue( &buf, msg );
      Reply(courier_tid, NULL, 0);
     
      if (!IobufEmpty(&request_queue)) {
        IobufDequeue(&request_queue, &i_min);
        IobufDequeue(&buf, &(reply.ch));
        Reply( i_min, (char*)&reply, sizeof(rx_reply) );
      }
    } else {
      IobufEnqueue(&request_queue, sender_tid);
      if (!IobufEmpty(&buf)) {
        IobufDequeue(&request_queue, &i_min);
        IobufDequeue(&buf, &(reply.ch));
        Reply( i_min, (char*)&reply, sizeof(rx_reply) );
      }
    }
    uart1->ctrl |= RIEN_MASK;
  }
}

void TrainTxNotifier() {
   
  iobuf buf;
  IobufInit(&buf);
 
  int comserver_tid = WhoIs("train-com-server");
 
  for ( ; ; ) {
    AwaitEvent( EVENT_UART1_TX, NULL, 0 );
    Send(comserver_tid, NULL, 0, NULL, 0);
  }
  Exit();
}

void TrainTransmitter() {
  iobuf buf;
  IobufInit(&buf);
 
  train_tx_reply reply;
 
  uart *const uart1 = (uart*)( UART1_BASE );  

  int server_tid = WhoIs("train-tx-server");
  int comserver_tid = WhoIs("train-com-server");
 
  int c, i;      

  for (;;) {    
    if (!IobufEmpty(&buf)) {    
      Send(comserver_tid, NULL, 0, (char*)&reply, sizeof(train_tx_reply));
      for (i = 0; i < reply.len; i++) {
        IobufEnqueue( &buf, reply.buf[i] );
      }
      Send(comserver_tid, NULL, 0, (char*)&reply, sizeof(train_tx_reply));
      for (i = 0; i < reply.len; i++) {
        IobufEnqueue( &buf, reply.buf[i] );
      }
       
      IobufDequeue( &buf, &c );
      uart1->data = c;
    } else {            
      // request more characters
      Send(server_tid, NULL, 0, (char*)&reply, sizeof(train_tx_reply));            
     
      if (IobufFull(&buf))
        Panic("TrainTransmitter", "IO buffer full");
      for (i = 0; i < reply.len; i++) {
        IobufEnqueue( &buf, reply.buf[i] );
      }
    }
  }
  Exit();
}

void TrainComServer() {
  train_tx_req msg;
  train_tx_reply reply;

  int sender_tid;
  int new_state;
  int state = TXREADY;

  int txmitter_tid = WhoIs("train-txmitter");
  int notifier_tid = Create( 7, &TrainTxNotifier, "train-tx-notifier" );
  Execute(notifier_tid);
 
  uart *const uart1 = (uart*)( UART1_BASE );
 
  for ( ; ; ) {
    Receive( &sender_tid, (char*)&msg, sizeof(train_tx_req));
   
    if (sender_tid == notifier_tid) {    
      // MS interrupt occurred      
      Reply( notifier_tid, NULL, 0 ); // early reply
     
      if (uart1->flag & CTS_MASK) {
        new_state = TXREADY;
      } else {
        new_state = STALLED;
      }
   
      if (state == STALLED && new_state == TXREADY) {
        state = new_state;
        reply.len = 0;
        Reply( txmitter_tid, (char*)&reply, sizeof(train_tx_reply) );
      }
      else {
        state = new_state;
      }
    } else {
      // transmitter asking for status
      if (state == TXREADY) {
        Reply( txmitter_tid, NULL, 0 );  
      }
    }
  }
  Exit();
}


void TrainTxServer() {
  iobuf buf;
  IobufInit(&buf);
 
  train_tx_reply reply;
  char msg;
 
  int sender_tid;
  int txmitter_empty = TRUE;  
 
  int train_com_tid = Create(4, &TrainComServer, "train-com-server");
  int txmitter_tid = Create(7, &TrainTransmitter, "train-txmitter");
  Execute(train_com_tid);  
  Execute(txmitter_tid);
   
  for ( ; ; ) {
    Receive( &sender_tid, (char*)&msg, sizeof(train_tx_req));
    // bwprintf(COM2, "TrainTXServer sender = %d\r\n", sender_tid);
    if (sender_tid == txmitter_tid) {
      // transmitter asking for more characters        
      if (!IobufEmpty(&buf)) {
        int i;
        reply.len = (IobufSize(&buf) < 16 ? IobufSize(&buf) : 16);
        for (i = 0; i < reply.len; i++) {
          IobufDequeue( &buf, &(reply.buf[i]) );
        }          
       
        txmitter_empty = FALSE;
        Reply( txmitter_tid, (char*)&reply, sizeof(train_tx_reply) );        
      } else {
        txmitter_empty = TRUE;
      }      
    } else {
      // system call request received!
      if (IobufFull(&buf))
        Panic("TrainTxServer", "IO buffer full");
      IobufEnqueue(&buf, msg);
      Reply( sender_tid, NULL, 0 );
     
      if (txmitter_empty && !IobufEmpty(&buf)) {        
        int i;
        reply.len = (IobufSize(&buf) < 16 ? IobufSize(&buf) : 16);
        for (i = 0; i < reply.len; i++) {
          IobufDequeue( &buf, &(reply.buf[i]) );
        }    
       
        txmitter_empty = FALSE;
        Reply( txmitter_tid, (char*)&reply, sizeof(train_tx_reply) );    
      }
    }
  }
  Exit();
}

